using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Collections.Specialized;
using System.Drawing.Imaging;

namespace Speedball2 {
  public class Match {
    public static Match _instance;
    public static bool _noDrawing = false;

    private BitVector32 _flags;
    private int _bit0_isComputerPlayer1;
    private int _bit1_isComputerPlayer2;
    private int _bit2_isBallInScoreMultiplier;
    private int _bit3_isPlayerInjured;
    private int _bit4_isFastForward;
    private int _bit5_isLauncherAnimStarted;
    private int _bit6_exitMatch;
    private int _bit7_isClockPaused;

    public int _creditsEarnedTeam1;
    public int _creditsEarnedTeam2;
    public int _maxCreditsEarnableTeam1;
    public int _maxCreditsEarnableTeam2;
    public int _coinsCredits;
    private int _scoreTeam1;
    private int _scoreTeam2;
    private bool _reverseSide;
    private int _timeInOpponentsHalfTeam1;
    private int _timeInOpponentsHalfTeam2;
    private int _bonusPointsTeam1;
    private int _bonusPointsTeam2;
    private ScoreMultipliers _scoreMultipliers;
    private Electrobounces _electrobounces;
    private BounceDomes _bounceDomes;
    private Stars _stars;

    private Bitmap _field;
    //private Palette _palette;

    //private Entity _medic1;
    //private Entity _medic2;
    //private Entity _ball;
    private Bitmap _screen;
    private Graphics _g;
    private int _viewHeight;
    private Point _scrollXY;

    private int _leg;
    private GameTypeEnum _gameType;
    private TextPanel _textPanel;

    private Entity _entityItemCoins1 = new Entity (Game.AtariDisk, Game.AtariRamToDisk (0x488A));
    private Entity _entityItemCoins2 = new Entity (Game.AtariDisk, Game.AtariRamToDisk (0x48B2));
    private Entity _entityItemCoins3 = new Entity (Game.AtariDisk, Game.AtariRamToDisk (0x48DA));
    private Entity _entityItemCoins4 = new Entity (Game.AtariDisk, Game.AtariRamToDisk (0x4902));
    private Entity _entityItemToken1 = new Entity (Game.AtariDisk, Game.AtariRamToDisk (0x492A));
    private Entity _entityItemToken2 = new Entity (Game.AtariDisk, Game.AtariRamToDisk (0x4952));
    private Entity _entityItemEquipment = new Entity (Game.AtariDisk, Game.AtariRamToDisk (0x497A));
    private Entity _entityBall = new Entity (Game.AtariDisk, Game.AtariRamToDisk (0x4812));
    private Entity _entityBallHighLauncher = new Entity (Game.AtariDisk, Game.AtariRamToDisk (0x4862));
    private Entity _entityTerrainCenter = new Entity (Game.AtariDisk, Game.AtariRamToDisk (0x5406));
    private Entity _entityDeck = new Entity (Game.AtariDisk, Game.AtariRamToDisk (0x483A));
    private Entity _entityGoalIndicatorLeft = new Entity (Game.AtariDisk, Game.AtariRamToDisk (0x49A2));
    private Entity _entityGoalIndicatorRight = new Entity (Game.AtariDisk, Game.AtariRamToDisk (0x49CA));
    private Entity _entityPlayerIndicatorTeam1 = new Entity (Game.AtariDisk, Game.AtariRamToDisk (0x49F2));
    private Entity _entityPlayerIndicatorTeam2 = new Entity (Game.AtariDisk, Game.AtariRamToDisk (0x4A1A));
    private Entity _entityMedic1 = new Entity (Game.AtariDisk, Game.AtariRamToDisk (0x53B6));
    private Entity _entityMedic2 = new Entity (Game.AtariDisk, Game.AtariRamToDisk (0x53DE));
    private Entity _entityStatusLed = new Entity (Game.AtariDisk, Game.AtariRamToDisk (0x4A42));

    private Entity _entityHoldingBall;

    private int _legDuration;
    private int _legRemainingTime;
    private int _remainingTimeInTheSecond;
    private int _timeSpeed;

    private Player [] _playersTeam1;
    private Player [] _playersTeam2;
    public Attributes [] _personAttributesTeam1;
    public Attributes [] _personAttributesTeam2;
    public Attributes _personAttributesTemp;
    public Attributes [] _personAttributesMarket;
    public Person [] _personsTeam1;
    public Person [] _personsTeam2;
    public Person _personTemp;
    public Team _team1;
    public Team _team2;

    private Player _playerHavingInputTeam1;
    private Player _playerHavingInputTeam2;
    private Player _playerHavingLedsTeam1;
    private Player _playerHavingLedsTeam2;

    private Player _playerThrower;

    private int _ballSpeedUnk;
    public DirBits _dirBitsA;
    public DirBits _dirBitsB;
    public Input _input1;
    public Input _input2;

    private int _flashingDurationA;
    private int _flashingDurationB;
    private int _flashingTimer;

    public Token _token;

    public int _tackleUnk01;
    public int _tackleUnk02;

    private Entity [] _orderedEntities;
    public Player _playerInjured;
    public Point _unk10Injured;
    public Point _unk11Injured;
    public int _injuryState;

    public int _equipmentOnFloorSpriteIndex;
    public int _equipmentNbPickupRemaining;

    public int _electrobouncedBallRemainingHits;

    public Match (MatchParam matchParam) {
      matchParam.Check ();

      _instance = this;
      Size viewSize = new Size (320, 200);
      _screen = new Bitmap (viewSize.Width, viewSize.Height);
      _g = Graphics.FromImage (_screen);
      _input1 = new Input ();
      _input2 = new Input ();
      _flashingDurationA = 6;
      _flashingDurationB = 2;
      _flashingTimer = _flashingDurationA;
      _timeSpeed = 2;
      _field = Field.Build ();
      //_palette = matchParam.palette;
      _token = new Token ();
      _tackleUnk01 = 16;
      _tackleUnk02 = 32;
      _maxCreditsEarnableTeam1 = matchParam.maxCreditsEarnableTeam1;
      _maxCreditsEarnableTeam2 = matchParam.maxCreditsEarnableTeam1;
      _coinsCredits = matchParam.coinsCredits;
      _equipmentOnFloorSpriteIndex = 0;
      _equipmentNbPickupRemaining = 0;
      _team1 = matchParam.team1;
      _team2 = matchParam.team2;
      _unk10Injured = Point.Empty;
      _unk11Injured = Point.Empty;
      _injuryState = -1;
      _electrobouncedBallRemainingHits = 0;

      _gameType = matchParam.gameType;
      _legDuration = matchParam.legDuration;
      _playersTeam1 = matchParam.playersTeam1;
      _playersTeam2 = matchParam.playersTeam2;
      Player.InitPresentationXY (this);
      _personAttributesTeam1 = matchParam.attributesTeam1;
      _personAttributesTeam2 = matchParam.attributesTeam2;
      _personAttributesTemp = matchParam.attributesTemp;
      _personAttributesMarket = matchParam.attributesMarket;
      _personsTeam1 = matchParam.personsTeam1;
      _personsTeam2 = matchParam.personsTeam2;
      _personTemp = matchParam.personTemp;

      int flags = 0;
      _bit0_isComputerPlayer1 = BitVector32.CreateMask ();
      _bit1_isComputerPlayer2 = BitVector32.CreateMask (_bit0_isComputerPlayer1);
      _bit2_isBallInScoreMultiplier = BitVector32.CreateMask (_bit1_isComputerPlayer2);
      _bit3_isPlayerInjured = BitVector32.CreateMask (_bit2_isBallInScoreMultiplier);
      _bit4_isFastForward = BitVector32.CreateMask (_bit3_isPlayerInjured);
      _bit5_isLauncherAnimStarted = BitVector32.CreateMask (_bit4_isFastForward);
      _bit6_exitMatch = BitVector32.CreateMask (_bit5_isLauncherAnimStarted);
      _bit7_isClockPaused = BitVector32.CreateMask (_bit6_exitMatch);
      _flags [_bit0_isComputerPlayer1] = ((flags >> 0) & 1) == 1;
      _flags [_bit1_isComputerPlayer2] = ((flags >> 1) & 1) == 1;
      _flags [_bit2_isBallInScoreMultiplier] = ((flags >> 2) & 1) == 1;
      _flags [_bit3_isPlayerInjured] = ((flags >> 3) & 1) == 1;
      _flags [_bit4_isFastForward] = ((flags >> 4) & 1) == 1;
      _flags [_bit5_isLauncherAnimStarted] = ((flags >> 5) & 1) == 1;
      _flags [_bit6_exitMatch] = ((flags >> 6) & 1) == 1;
      _flags [_bit7_isClockPaused] = ((flags >> 7) & 1) == 1;

      // menu init
      Bit0_IsComputerPlayer1 = matchParam.isComputerPlayer1;
      Bit1_IsComputerPlayer2 = matchParam.isComputerPlayer2;

      // match init
      _creditsEarnedTeam1 = matchParam.minCreditsEarnable;
      _creditsEarnedTeam2 = matchParam.minCreditsEarnable;

      //_scrollXY = new Point (160, 484);
      _viewHeight = 184;
      _scrollXY = new Point ((_field.Width - _screen.Width) / 2, (_field.Height - _viewHeight) / 2);

      if (_gameType == GameTypeEnum.Practice)
        _leg = 3;
      else if (_gameType == GameTypeEnum.Demo)
        _leg = 1;
      else
        _leg = 0;

      // todo 0xAE24: switch side

      Bit7_IsClockPaused = true;
      _textPanel = new TextPanel ();
      _textPanel._text = TextEnum.VS;
      // todo 0xAE44: replay
      _entityItemCoins1.InitItemCoins ();
      _entityItemCoins2.InitItemCoins ();
      _entityItemCoins2._timer = _entityItemCoins1._timer;
      _entityItemCoins3.InitItemCoins ();
      _entityItemCoins3._timer = _entityItemCoins1._timer;
      _entityItemCoins4.InitItemCoins ();
      _entityItemCoins4._timer = _entityItemCoins1._timer;
      _token.Dispose (this);
      _entityItemToken1.InitItemToken (_gameType);
      _entityItemToken2.InitItemToken (_gameType);
      _entityItemEquipment.InitItemEquipment (this);
      _scoreTeam1 = 0;
      _scoreTeam2 = 0;
      _reverseSide = false;
      _timeInOpponentsHalfTeam1 = 0;
      _timeInOpponentsHalfTeam2 = 0;
      _bonusPointsTeam1 = 0;
      _bonusPointsTeam2 = 0;
      _stars = new Stars ();
      _scoreMultipliers = new ScoreMultipliers ();
      _electrobounces = new Electrobounces ();
      _bounceDomes = new BounceDomes ();
      _legRemainingTime = _legDuration;
      _remainingTimeInTheSecond = 50;

      if (matchParam.initialDirTeam1 != 0)
        SwitchSide ();
      int i = 0;
      _orderedEntities = new Entity [20];
      foreach (Player player in _playersTeam1)
        _orderedEntities [i++] = player;
      foreach (Player player in _playersTeam2)
        _orderedEntities [i++] = player;
      _orderedEntities [i++] = _entityMedic1;
      _orderedEntities [i++] = _entityMedic2;

      // hack: additional init
      _playerHavingInputTeam1 = _playersTeam1 [8];
      _playerHavingInputTeam2 = _playersTeam2 [8];
      _playerHavingLedsTeam1 = null;
      _playerHavingLedsTeam2 = null;
      _playerInjured = null;

      InitPlayersAttributesAndStats (_playersTeam1);
      InitPlayersAttributesAndStats (_playersTeam2);
      DrawStatusBackground ();
      DrawStatusScores ();
      InitPlayersZoneCenterXY (_playersTeam1);
      InitPlayersZoneCenterXY (_playersTeam2);
      DrawTerrainWallsAndBackground ();
      DrawSprites ();
      _entityHoldingBall = null;



      //foreach (Player player in _playersTeam1)
      //  player._terrainXY = player._presentationXY;
      //foreach (Player player in _playersTeam1)
      //  player._spriteIndexUnk02 = Game.Random (10);
      //foreach (Player player in _playersTeam2)
      //  player._spriteIndexUnk02 = Game.Random (10);
      //DrawZones ();
      //Game.ReferenceEquals (null, null);
    }

    //public void SetViewSize (Size size) {
    //  if (_screen != null) {
    //    _screen.Dispose ();
    //    _g.Dispose ();
    //  }
    //  _screen = new Bitmap (size.Width, size.Height);
    //  _g = Graphics.FromImage (_screen);

    //}

    private void DrawZones () {

      //g.DrawEllipse (Pens.Red, player._zoneCenterXY.X - r, player._zoneCenterXY.Y - r, r * 2 + 1, r * 2 + 1);
      //g.DrawEllipse (Pens.Yellow, player._launchXY.X - r, player._launchXY.Y - r, r * 2 + 1, r * 2 + 1);
      Graphics g = Graphics.FromImage (_field);
      DrawTeamTactic (g, _playersTeam2, Pens.Red, SpriteManager.GetBank (BankEnum.PlayersSpr) [4], true);
      DrawTeamTactic (g, _playersTeam1, Pens.Blue, SpriteManager.GetBank (BankEnum.PlayersSprBlue) [0], false);
      _field.Save ("toto.png", ImageFormat.Png);
    }

    private void DrawTeamTactic (Graphics g, Player [] players, Pen pen, Bitmap b, bool playerOnly) {
      string [] pos = new string [9];
      for (int j = 0; j < 9; j++) {
        pos [j] = "";
        for (int i = 0; i < 10; i++)
          pos [j] = pos [j].Insert (pos [j].Length, ((char) Game.AtariDisk.ReadByte (Game.AtariRamToDisk (0xFA2 + j * 10 + i))).ToString ());
        pos [j] = pos [j].Trim ();
      }

      if (!playerOnly)
        foreach (Player player in players)
          g.DrawRectangle (Pens.Red, player._zoneXY1.X + 1, player._zoneXY1.Y + 1, player._zoneXY2.X - 1 - player._zoneXY1.X - 2, player._zoneXY2.Y - 1 - player._zoneXY1.Y - 2);
      if (!playerOnly)
        foreach (Player player in players)
          g.DrawArrowOutlinedSolid (pen, player._launchXY.X, player._launchXY.Y, player._zoneCenterXY.X, player._zoneCenterXY.Y);
      foreach (Player player in players)
        g.DrawImage (b, player._launchXY.X - 16, player._launchXY.Y - 16, 0.5f);
      foreach (Player player in players)
        g.DrawImage (b, player._zoneCenterXY.X - 16, player._zoneCenterXY.Y - 16);
      if (!playerOnly)
        foreach (Player player in players)
          g.DrawStringOutlinedCentered (pos [player._index], Game.Font, Brushes.White, Brushes.Black, player._zoneCenterXY.X, player._zoneCenterXY.Y);
    }

    private void InitPlayersAttributesAndStats (Player [] players) {
      foreach (Player player in players)
        player.InitAttributesAndStats (this);
    }

    private void InitPlayersZoneCenterXY (Player [] players) {
      foreach (Player player in players)
        player.InitZoneCenterXY ();
    }

    public int _frame = 0;
    public bool Iterate () {
      _frame++;



      //foreach (Player player in _playersTeam1)
      //  player.Dump ();
      //foreach (Player player in _playersTeam2)
      //  player.Dump ();
      Update ();

      bool isMatchOverPlayersInPlace = false;
      if (_leg == 2) {
        if (_scrollXY.Y == 484) {
          if (!Bit5_IsLauncherAnimStarted) {
            // wait match over formation
            bool arePlayersInPlace = true;
            int presentationIndex = 0;
            if (_playersTeam1 [0]._attributes._initialDir != 0)
              presentationIndex = 9;
            foreach (Player player in _playersTeam1) {
              if (player._terrainXY != Player.GetPresentationXY (presentationIndex + player._index)) {
                arePlayersInPlace = false;
                break;
              }
              if (player._dir != player._attributes._initialDir) {
                arePlayersInPlace = false;
                break;
              }
            }
            if (arePlayersInPlace) {
              if (_gameType != GameTypeEnum.Practice) {
                presentationIndex = 0;
                if (_playersTeam1 [0]._attributes._initialDir == 0)
                  presentationIndex = 9;
                foreach (Player player in _playersTeam2) {
                  if (player._terrainXY != Player.GetPresentationXY (presentationIndex + player._index)) {
                    arePlayersInPlace = false;
                    break;
                  }
                  if (player._dir != player._attributes._initialDir) {
                    arePlayersInPlace = false;
                    break;
                  }
                }
                if (arePlayersInPlace)
                  isMatchOverPlayersInPlace = true;
              }
              // practice
              else {
                isMatchOverPlayersInPlace = true;
              }
            }
          }
        }
      }

      // match not over
      if (!isMatchOverPlayersInPlace) {
        // todo: pause key, F, ...


      }
      if (Game._debug)
        _g.DrawStringOutlined (_frame.ToString (), Game.Font, Brushes.White, Brushes.Black, 0, 0);
      //Console.WriteLine ("{0}: {1}", _frame, _input1.Bit2_IsLeft);
      //Console.WriteLine ("Frame: {0}", _frame);
      //Console.WriteLine ("Def: {0}", _playersTeam1 [2]._attributes._def);
      //_entityItemEquipment.Dump ();
      //_entityItemCoins1.Dump ();
      //_entityItemCoins2.Dump ();
      //_entityItemCoins3.Dump ();
      //_entityItemCoins4.Dump ();
      //Console.WriteLine ("Coins1: {0}", _entityItemCoins1._terrainXY);
      //Console.WriteLine ("Coins2: {0}", _entityItemCoins2._terrainXY);
      //Console.WriteLine ("Coins3: {0}", _entityItemCoins3._terrainXY);
      //Console.WriteLine ("Coins4: {0}", _entityItemCoins4._terrainXY);
      //Console.WriteLine ();
      return (isMatchOverPlayersInPlace);
    }

    public void DumpState (List<Entity> entities) {
      Game.myRandom.Dump ();
      SaveScreen ();
      if (entities.Count > 0)
        foreach (Entity entity in entities) {
          if (entity is Player)
            entity.GetPlayer.Dump ();
          else
            entity.Dump ();
          Console.WriteLine ();
        }
      else
        Player.DumpAllPlayers ();
      //Console.WriteLine ("Coins1:");
      //_entityItemCoins1.Dump ();
      //Console.WriteLine ("Coins2:");
      //_entityItemCoins2.Dump ();
      //Console.WriteLine ("Coins3:");
      //_entityItemCoins3.Dump ();
      //Console.WriteLine ("Coins4:");
      //_entityItemCoins4.Dump ();
    }

    private void SaveScreen () {
      _screen.Save ("screen.png", ImageFormat.Png);
    }

    private void Update () {
      //Console.WriteLine ("A " + _playersTeam2 [7].Bit0);
      //if ((goal == true) && (i == 7)) {
      //  int j = 0;
      //}
      if (Bit7_IsClockPaused) {
        HandleAnimGoalToInitLaunch ();
        HandleDeck ();
      }
      if (!_entityBall.Bit7_IsInvisible) {
        _entityBall.UpdateBallVelocity (this);
        _scoreMultipliers.CheckEnter (this);
        _scoreMultipliers.UpdateInside (this);
        _stars.CheckHit (this);
        WarpGateCheckEnter ();
        _bounceDomes.CheckHit (this);
        _electrobounces.CheckHit (this);
        UpdateDistanceBallToPlayers ();
        SetPlayersHavingInput ();
      }
      AnimMedic ();
      CalcDistanceBetweenOnscreenPlayers ();
      EntitiesThink ();
      CheckGoal ();
      MoveBallPlayersMedicsHandleWallsAndBounce ();

      // player indicators
      if (!Bit7_IsClockPaused) {
        _entityPlayerIndicatorTeam1._terrainXY.X = _playerHavingInputTeam1._terrainXY.X;
        _entityPlayerIndicatorTeam1._terrainXY.Y = _playerHavingInputTeam1._terrainXY.Y + _playerHavingInputTeam1._originXY.Y;
        _entityPlayerIndicatorTeam1._spriteIndex = 33;
        if (_entityHoldingBall == _playerHavingInputTeam1)
          _entityPlayerIndicatorTeam1._spriteIndex = 28 + _playerHavingInputTeam1._attributes._pos;

        _entityPlayerIndicatorTeam2._terrainXY.X = _playerHavingInputTeam2._terrainXY.X;
        _entityPlayerIndicatorTeam2._terrainXY.Y = _playerHavingInputTeam2._terrainXY.Y + _playerHavingInputTeam2._originXY.Y;
        _entityPlayerIndicatorTeam2._spriteIndex = 27;
        if (_entityHoldingBall == _playerHavingInputTeam2)
          _entityPlayerIndicatorTeam2._spriteIndex = 22 + _playerHavingInputTeam2._attributes._pos;
      }

      Entity entityToCenterOn = _entityBall;
      if (_entityHoldingBall != null) {
        if (!Bit7_IsClockPaused) {
          _entityBall._terrainXY = _entityHoldingBall._terrainXY;
          _entityBall._velocityXY = _entityHoldingBall._velocityXY;
          Point ballDeltaXY = Player.GetBallDeltasXY (_entityHoldingBall._spriteIndex);
          int deltaX = _entityHoldingBall._originXY.X + ballDeltaXY.X - 8;
          int deltaY = _entityHoldingBall._originXY.Y + ballDeltaXY.Y - 8;
          if (_entityBall._spriteIndex == 0) {
            deltaX -= 4;
            deltaY -= 4;
          }
          _entityBall._terrainXY.X += deltaX;
          _entityBall._terrainXY.Y += deltaY;
        }
        entityToCenterOn = _entityHoldingBall;
      }

      CenterScreenOnEntity (entityToCenterOn);
      DrawStatusLeds ();
      DrawMain ();
    }

    private void AnimMedic () {
      if (_entityMedic1.Bit7_IsInvisible)
        return;
      if (_injuryState == 0)
        AnimMedic_Step0_Coming (_playerInjured);
      else if (_injuryState == 1)
        AnimMedic_Step1_FirstArrived (_playerInjured);
      else if (_injuryState == 2)
        AnimMedic_Step2_FirstPickup (_playerInjured);
      else if (_injuryState == 3)
        AnimMedic_Step3_SecondPickup (_playerInjured);
      else
        throw new Exception ();
    }

    private void AnimMedic_Step0_Coming (Player playerInjured) {
      Entity medic = _entityMedic2;
      DirBits dirBits = medic.GetDirBitsToTargetSimple (_unk11Injured);
      if (dirBits.HasDirection) {
        medic._dir = Entity.GetDirFromDirBits (dirBits);
        medic.sub_F5A8_SetOpcodesAndVelocityFromDir (0x6066, 2);
        medic._opcodeIndex = 0;

        medic = _entityMedic1;
        Point targetXY = _unk10Injured;
        targetXY.X += playerInjured._terrainXY.X;
        dirBits = medic.GetDirBitsToTargetSimple (targetXY);
        int dir = Entity.GetDirFromDirBits (dirBits);
        if (dir < 0)
          medic._velocityXY = Point.Empty;
        else {
          medic._dir = dir;
          medic.sub_F5A8_SetOpcodesAndVelocityFromDir (0x6066, 2);
          medic._opcodeIndex = 0;
        }
      }
      else {
        _unk11Injured.Y = playerInjured._terrainXY.Y;
        if (playerInjured._terrainXY.X <= 320) {
          _unk10Injured.X = -26;
          _unk11Injured.X -= 4;
        }
        else {
          _unk10Injured.X = 26;
          _unk11Injured.X += 4;
        }
        _injuryState++;
        AnimMedic_Step1_FirstArrived (playerInjured);
      }
    }

    private void AnimMedic_Step1_FirstArrived (Player playerInjured) {
      Entity medic = _entityMedic2;
      DirBits dirBits = medic.GetDirBitsToTargetSimple (_unk11Injured);
      if (dirBits.HasDirection) {
        medic._dir = Entity.GetDirFromDirBits (dirBits);
        medic.sub_F5A8_SetOpcodesAndVelocityFromDir (0x6066, 2);

        medic = _entityMedic1;
        Point targetXY = _unk10Injured;
        targetXY.X += playerInjured._terrainXY.X;
        dirBits = medic.GetDirBitsToTargetSimple (targetXY);
        int dir = Entity.GetDirFromDirBits (dirBits);
        if (dir < 0)
          medic._velocityXY = Point.Empty;
        else {
          medic._dir = dir;
          medic.sub_F5A8_SetOpcodesAndVelocityFromDir (0x6066, 2);
          medic._opcodeIndex = 0;
        }
      }
      else {
        _textPanel._text = TextEnum.Injured;
        _textPanel._unk01 = 0;
        Text text = TextPanel.GetText (TextEnum.Injured);
        for (int i = 0; i < 10; i++)
          text.Bytes [2 + i] = (byte) Game.AtariDisk.ReadByte (Game.AtariRamToDisk (0xFA2 + playerInjured._index * 10 + i));
        _entityMedic2._velocityXY.Y = 0;
        _unk10Injured.Y = _unk11Injured.Y;
        if (playerInjured._terrainXY.X <= 320) {
          playerInjured._velocityXY.X = 2;
          _entityMedic2._velocityXY.X = 2;
          _entityMedic2._pOpcodes = 0x5DAA;
          _unk11Injured.X = 592;
        }
        else {
          _entityMedic2._velocityXY.X = -2;
          playerInjured._velocityXY.X = -2;
          _entityMedic2._pOpcodes = 0x5DA6;
          _unk11Injured.X = 48;
        }
        _injuryState++;
        AnimMedic_Step2_FirstPickup (playerInjured);
      }
    }

    private void AnimMedic_Step2_FirstPickup (Player playerInjured) {
      Entity medic = _entityMedic1;
      Point targetXY = _unk10Injured;
      targetXY.X += playerInjured._terrainXY.X;
      DirBits dirBits = medic.GetDirBitsToTargetSimple (targetXY);
      if (targetXY.Y != medic._terrainXY.Y) {
        int dir = Entity.GetDirFromDirBits (dirBits);
        if (dir < 0)
          dir = 0;
        medic._dir = dir;
        medic.sub_F5A8_SetOpcodesAndVelocityFromDir (0x6066, 2);
      }
      else {
        playerInjured._pOpcodes = 0x5B76;
        if (_entityMedic2._pOpcodes == 0x5DA6) {
          _entityMedic1._pOpcodes = 0x5DB6;
          _entityMedic2._pOpcodes = 0x5DAE;
        }
        else {
          _entityMedic1._pOpcodes = 0x5DAE;
          _entityMedic2._pOpcodes = 0x5DB6;
        }
        _injuryState++;
        AnimMedic_Step3_SecondPickup (playerInjured);
      }
    }

    private void AnimMedic_Step3_SecondPickup (Player playerInjured) {
      _entityMedic1._velocityXY = _entityMedic2._velocityXY;
      if (((_entityMedic1._terrainXY.X + 16) >= _scrollXY.X) &&
          ((_entityMedic1._terrainXY.X + 16 - 352) <= _scrollXY.X))
        return;
      Bit3_IsPlayerInjured = false;
      SubstitutePlayer (playerInjured);
      _textPanel._text = TextEnum.VS;
      int substitutions = playerInjured._substitutions;
      playerInjured.InitAttributesAndStats (this);
      playerInjured._substitutions = substitutions + 1;
      playerInjured._timer = 25;
      playerInjured._targetXY = new Point (320, 576);
      playerInjured.Bit0_IsBusy = false;
      playerInjured._terrainXY.Y = 576;
      if (playerInjured._terrainXY.X <= 320)
        playerInjured._terrainXY.X = 48;
      else
        playerInjured._terrainXY.X = 592;
      if (playerInjured._attributes._initialDir == 0)
        playerInjured._terrainXY.Y += 32;
      else
        playerInjured._terrainXY.Y -= 32;

      playerInjured.sub_EB7A (this);
      _playerInjured = null;
      _entityTerrainCenter.Bit0_IsBusy = false;
      _entityMedic1.Bit7_IsInvisible = true;
      _entityMedic2.Bit7_IsInvisible = true;
      DrawStatusScores ();
    }

    private void SubstitutePlayer (Player playerInjured) {
      Attributes attributesInjured = playerInjured.GetPersonAttributes (this);
      attributesInjured._agr = playerInjured._attributes._agr;
      attributesInjured._att = playerInjured._attributes._att;
      attributesInjured._def = playerInjured._attributes._def;
      attributesInjured._spd = playerInjured._attributes._spd;
      attributesInjured._thr = playerInjured._attributes._thr;
      attributesInjured._pow = playerInjured._attributes._pow;
      attributesInjured._sta = playerInjured._attributes._sta;
      attributesInjured._int = playerInjured._attributes._int;


      Person personInjured;
      Person [] personsTeamInjured;
      if (playerInjured.Bit3_IsTeam1_IsElectrobounced) {
        personInjured = _personsTeam1 [playerInjured._index];
        personsTeamInjured = _personsTeam1;
      }
      else {
        personInjured = _personsTeam2 [playerInjured._index];
        personsTeamInjured = _personsTeam2;
      }

      personInjured.GetPersonAttributes (this).DoMultipleOf10 ();

      personInjured.CopySubstituteTo (this, _personTemp);
      personsTeamInjured [9].CopySubstituteTo (this, personInjured);
      personsTeamInjured [10].CopySubstituteTo (this, personsTeamInjured [9]);
      personsTeamInjured [11].CopySubstituteTo (this, personsTeamInjured [10]);
      _personTemp.CopySubstituteTo (this, personsTeamInjured [11]);
    }

    private void SwitchSide () {
      for (int i = 0; i < 9; i++) {
        Player player1 = _playersTeam1 [i];
        Player player2 = _playersTeam2 [i];
        ExtensionMethods.Swap (ref player1._zoneXY1, ref player2._zoneXY1);
        ExtensionMethods.Swap (ref player1._zoneXY2, ref player2._zoneXY2);
        ExtensionMethods.Swap (ref player1._launchXY, ref player2._launchXY);
        ExtensionMethods.Swap (ref player1._attributes._initialDir, ref player2._attributes._initialDir);
        ExtensionMethods.Swap (ref _personAttributesTeam1 [player1._personAttributesIndex]._initialDir, ref _personAttributesTeam2 [player2._personAttributesIndex]._initialDir);
      }
      InitPlayersZoneCenterXY (_playersTeam1);
      InitPlayersZoneCenterXY (_playersTeam2);
      _reverseSide = !_reverseSide;
      _stars.SwitchSide ();
    }

    private void WarpGateCheckEnter () {
      if (_entityHoldingBall != null)
        return;
      if (_entityBall._spriteIndex > 2)
        return;
      if ((_entityBall._terrainXY.Y < 355) || (_entityBall._terrainXY.Y > 385))
        if ((_entityBall._terrainXY.Y < 767) || (_entityBall._terrainXY.Y > 797))
          return;
      if (_entityBall._terrainXY.X < 32)
        _entityBall._terrainXY.X = 608;
      else if (_entityBall._terrainXY.X > 608)
        _entityBall._terrainXY.X = 32;
      else
        return;
      _entityBall._velocityXY = Match.GetVelocityXYFromDir (_entityBall._dir, 8);
      SetBallSpeedFromThr (_playerThrower._attributes._thr);
    }

    private void UpdateDistanceBallToPlayers () {
      if (Bit7_IsClockPaused)
        return;
      Entity entity = _entityBall;
      if (_entityHoldingBall != null)
        entity = _entityHoldingBall;
      entity.UpdateDistanceBallToPlayers (this, _playersTeam1);
      entity.UpdateDistanceBallToPlayers (this, _playersTeam2);
    }

    private void SetPlayersHavingInput () {
      if (Bit7_IsClockPaused)
        return;
      if (_playerHavingInputTeam1 != _entityHoldingBall)
        _playerHavingInputTeam1 = GetPlayerNearestToBall (_playersTeam1);
      if (_playerHavingInputTeam2 != _entityHoldingBall)
        _playerHavingInputTeam2 = GetPlayerNearestToBall (_playersTeam2);
    }

    public void SetBallSpeedFromThr (int d0) {
      _entityBall._timer = d0 / 2;
      _ballSpeedUnk = d0 / 4 + d0 / 8 + d0 / 16;
    }

    private Player GetPlayerNearestToBall (Player [] players) {
      int distance = int.MaxValue;
      Player nearestPlayer = null;
      foreach (Player player in players) {
        if (player._distanceToBall <= distance) {
          distance = player._distanceToBall;
          nearestPlayer = player;
        }
      }
      return (nearestPlayer);
    }

    private void CalcDistanceBetweenOnscreenPlayers () {
      if (Bit7_IsClockPaused)
        return;
      foreach (Player playerTeam1 in _playersTeam1) {
        if (playerTeam1.Bit6_IsOffscreen)
          continue;
        Point xy = new Point (playerTeam1._terrainXY.X + playerTeam1._originXY.X,
                              playerTeam1._terrainXY.Y + playerTeam1._originXY.Y);
        foreach (Player playerTeam2 in _playersTeam2) {
          if (playerTeam2.Bit6_IsOffscreen)
            continue;
          Point p2;
          int distance = playerTeam2.CalcVector (xy, out p2);
          playerTeam1._distanceToOpposingPlayers [playerTeam2._index] = distance;
          playerTeam2._distanceToOpposingPlayers [playerTeam1._index] = distance;
        }
      }
    }

    public bool goal = false;
    private void EntitiesThink () {
      for (int i = 0; i < 9; i++) {
        _playersTeam2 [i].Think (this);
        _playersTeam1 [i].Think (this);
      }
      _entityDeck.ExecuteOpcode (this);
      _bounceDomes.EntityTop.ExecuteOpcode (this);
      _bounceDomes.EntityBottom.ExecuteOpcode (this);
      _entityBallHighLauncher.ExecuteOpcode (this);
      _entityItemCoins1.sub_D214_ExecuteOpcodeCoins (this);
      _entityItemCoins2.sub_D214_ExecuteOpcodeCoins (this);
      _entityItemCoins3.sub_D214_ExecuteOpcodeCoins (this);
      _entityItemCoins4.sub_D214_ExecuteOpcodeCoins (this);
      _entityItemToken1.sub_D272_ExecuteOpcodeToken (this);
      _entityItemToken2.sub_D272_ExecuteOpcodeToken (this);
      _entityItemEquipment.sub_D2A4_ExecuteOpcodeEquipment (this);
      _entityBall.ExecuteOpcode (this);
      _entityMedic1.ExecuteOpcode (this);
      _entityMedic2.ExecuteOpcode (this);
    }

    private void CheckGoal () {
      if (_entityHoldingBall != null)
        return;
      if (Bit7_IsClockPaused)
        return;
      if (_entityBall._spriteIndex > 2)
        return;
      if (_entityBall._terrainXY.X < 272)
        return;
      if (_entityBall._terrainXY.X > 368)
        return;
      bool markingSide;
      if ((_entityBall._terrainXY.Y < 32) && (_entityBall._velocityXY.Y < 0)) {
        markingSide = true;
      }
      else if ((_entityBall._terrainXY.Y > 1120) && (_entityBall._velocityXY.Y > 0)) {
        markingSide = false;
      }
      else
        return;

      // hack
      if (_reverseSide)
        markingSide = !markingSide;

      if (!CheckGoalDoor (markingSide))
        return;
      bool ownGoal = false;
      if (_playerThrower.Bit3_IsTeam1_IsElectrobounced) {
        if (!markingSide) {
          _playerThrower = _playerHavingInputTeam2;
          _textPanel._text = TextEnum.OwnGoal;
          ownGoal = true;
        }
      }
      else {
        if (markingSide) {
          _playerThrower = _playerHavingInputTeam1;
          _textPanel._text = TextEnum.OwnGoal;
          ownGoal = true;
        }
      }
      if (!ownGoal)
        if (_playerThrower.Bit3_IsTeam1_IsElectrobounced)
          _textPanel._text = TextEnum.Team1Scored;
        else
          _textPanel._text = TextEnum.Team2Scored;

      _entityBall.Bit7_IsInvisible = true;
      IncScoreWithBonusPoints (markingSide, 10);
      _entityBall._terrainXY = new Point (320, 576);
      _entityBall._velocityXY = Point.Empty;
      //todo: replay goal
      Bit7_IsClockPaused = true;
      if (!Bit4_IsFastForward)
        _textPanel._text = TextEnum.None;
      _playerThrower._goalsScored++;
      _playerThrower.sub_CEEA_InitGoalerAnim (this);
      DrawStatusScores ();
      //Console.WriteLine ("GOAL");
      goal = true;
    }

    private bool CheckGoalDoor (bool markingSide) {
      if (_token._heldSpriteIndex != 63)
        return (true);
      if (_token._holdingPlayer._attributes._initialDir == 0)
        return (markingSide != _reverseSide);
      else
        return (markingSide == _reverseSide);
    }

    private void MoveBallPlayersMedicsHandleWallsAndBounce () {
      if (_entityHoldingBall == null) {
        if (!Bit2_IsBallInScoreMultiplier) {
          int margin = 24;
          if (_entityBall._spriteIndex <= 2)
            margin = 32;
          _entityBall.MoveAndHandleWallsAndBounce (margin);
        }
      }
      foreach (Player player in _playersTeam1)
        player.MoveAndHandleWallsAndBounce (48);
      foreach (Player player in _playersTeam2)
        player.MoveAndHandleWallsAndBounce (48);
      _entityMedic1.MoveAndHandleWallsAndBounce (48);
      _entityMedic2.MoveAndHandleWallsAndBounce (48);
    }

    private void SetClipHeight (int height) {
      //_g.ResetClip ();
      if (height < 0)
        //_g.ResetClip ();
        SetClipHeight (_screen.Height);
      else
        _g.Clip = new Region (new Rectangle (0, 0, _screen.Width, height));
    }

    private void DrawStatusBackground () {
      SetClipHeight (-1);
      _g.DrawImage (SpriteManager.GetBitmap (BitmapEnum.Status), 0, 184);
    }

    public void DrawStatusScores () {
      SetClipHeight (-1);
      DrawStatusScore (_scoreTeam1, 2);
      DrawStatusScore (_scoreTeam2, 35);
    }

    private void DrawStatusScore (int score, int x) {
      string scoreString = score.ToString ().PadLeft (3, '0');
      foreach (char c in scoreString) {
        _g.DrawImage (SpriteManager.GetBank (BankEnum.DigitSpr) [c - '0'], x * 8, 187);
        x++;
      }
    }

    private void DrawStatusRemainingTime () {
      SetClipHeight (-1);
      int x = 19;
      string remainingTimeString = _legRemainingTime.ToString ().PadLeft (2, '0');
      foreach (char c in remainingTimeString) {
        _g.DrawImage (SpriteManager.GetBank (BankEnum.DigitSpr) [c - '0'], x * 8, 186);
        x++;
      }
    }

    private void DrawStatusPlayersHealth () {
      SetClipHeight (-1);
      DrawStatusPlayerHealth (_playerHavingInputTeam1, 64, 193, SpriteManager.BlueBrush);
      DrawStatusPlayerHealth (_playerHavingInputTeam2, 224, 193, SpriteManager.RedBrush);
    }

    private void DrawStatusPlayerHealth (Player player, int x, int y, Brush brush) {
      int width = player._attributes._health / 4;
      _g.FillRectangle (SpriteManager.BackgroundBrush, x, y, 32, 3);
      _g.FillRectangle (brush, x, y, width, 3);
    }

    public void DrawStatusToken (Entity entity, Player player) {
      SetClipHeight (-1);
      int x;
      if (player == _playerHavingInputTeam1)
        x = 56;
      else
        x = 264;

      _entityStatusLed._spriteIndex = entity._spriteIndex;
      _entityStatusLed._terrainXY.X = x + _scrollXY.X;
      _entityStatusLed._terrainXY.Y = _viewHeight + 8 + _scrollXY.Y;
      _entityStatusLed.Draw (_g, _scrollXY);
    }

    private void DrawStatusLeds () {
      if (_playerHavingInputTeam1 == _playerHavingLedsTeam1)
        if ((_playerHavingInputTeam2 == _playerHavingLedsTeam2) || (_gameType == GameTypeEnum.Practice))
          return;
      DrawStatusLedsOff ();

      _playerHavingLedsTeam1 = _playerHavingInputTeam1;
      if (_playerHavingInputTeam1._attributes._pos <= 1) {
        _entityStatusLed._spriteIndex = 43;
        DrawStatusLedsOn (104);
      }
      else if (_playerHavingInputTeam1._attributes._pos == 2) {
        _entityStatusLed._spriteIndex = 44;
        DrawStatusLedsOn (120);
      }
      else {
        _entityStatusLed._spriteIndex = 45;
        DrawStatusLedsOn (136);
      }

      if (!_playerHavingInputTeam2.Bit7_IsInvisible) {
        _playerHavingLedsTeam2 = _playerHavingInputTeam2;
        if (_playerHavingInputTeam2._attributes._pos <= 1) {
          _entityStatusLed._spriteIndex = 48;
          DrawStatusLedsOn (216);
        }
        else if (_playerHavingInputTeam1._attributes._pos == 2) {
          _entityStatusLed._spriteIndex = 47;
          DrawStatusLedsOn (200);
        }
        else {
          _entityStatusLed._spriteIndex = 46;
          DrawStatusLedsOn (184);
        }
      }
    }

    private void DrawStatusLedsOn (int x) {
      SetClipHeight (-1);
      _entityStatusLed._terrainXY.X = _scrollXY.X + x;
      _entityStatusLed._terrainXY.Y = _scrollXY.Y + 8 + _viewHeight;
      _entityStatusLed.Draw (_g, _scrollXY);
    }

    private void DrawStatusLedsOff () {
      SetClipHeight (-1);
      _entityStatusLed._terrainXY.Y = _scrollXY.Y + 8 + _viewHeight;
      for (int i = 0; i < 6; i++) {
        _entityStatusLed._terrainXY.X = _scrollXY.X + GetStatusXY (i, out _entityStatusLed._spriteIndex);
        _entityStatusLed._spriteIndex += 6;
        _entityStatusLed.Draw (_g, _scrollXY);
      }
    }

    private static int GetStatusXY (int index, out int spriteIndex) {
      int offset = 0x2A8A + index * 4;
      spriteIndex = Game.AtariDisk.ReadWord (Game.AtariRamToDisk (offset + 2));
      return (Game.AtariDisk.ReadWord (Game.AtariRamToDisk (offset)));
    }

    private void DrawMain () {
      if (Bit4_IsFastForward) {
        // todo: fast forward
      }
      DrawStatusPlayersHealth ();
      HandleTime ();
      if (!Bit4_IsFastForward) {
        // todo: wait time speed
        DrawTerrainWallsAndBackground ();
        DrawSprites ();
      }

      if (!_noDrawing)
        if (Bit7_IsClockPaused) {
          SetClipHeight (_viewHeight);
          if (Bit3_IsPlayerInjured) {
            if (_injuryState <= 1)
              _g.DrawImage (SpriteManager.GetBitmap (BitmapEnum.PanelInjured), 16, 8);
            else
              _textPanel.DrawTextGeneric (this, _g);
          }
          else {
            if (_textPanel._text == TextEnum.None)
              _g.DrawImage (SpriteManager.GetBitmap (BitmapEnum.PanelGoal), 16, 8);
            else
              _textPanel.DrawTextGeneric (this, _g);
          }
          SetClipHeight (-1);
        }
    }

    private void HandleTime () {
      _remainingTimeInTheSecond -= _timeSpeed;
      if (_remainingTimeInTheSecond <= 0) {
        _remainingTimeInTheSecond = 50;
        _stars.CheckFullRowActivated (this);
        if (!Bit7_IsClockPaused) {
          _legRemainingTime--;
          IncrementTimeInPossession ();
        }
        if (_token._heldSpriteIndex != 0)
          _token._remainingSeconds--;
        DrawStatusRemainingTime ();
        if (_token._remainingSeconds == 0)
          _token.Dispose (this);
        if (!Bit7_IsClockPaused) {
          if (_legRemainingTime == 0) {
            SwitchSide ();
            _leg++;
            Bit7_IsClockPaused = true;
            _legRemainingTime = _legDuration;
            if (_leg != 2) {
              _textPanel._text = TextEnum.HalfTime;
              _textPanel._unk01 = 0;
              Text text = TextPanel.GetText (TextEnum.HalfTime);
              text.WriteNumber (_scoreTeam1, 14, 3);
              text.WriteNumber (_scoreTeam2, 21, 3);
            }
            else {
              _textPanel._text = TextEnum.MatchOver;
              _textPanel._unk01 = 0;
              Text text = TextPanel.GetText (TextEnum.MatchOver);
              text.WriteNumber (_scoreTeam1, 23, 3);
              text.WriteNumber (_scoreTeam2, 30, 3);
            }
          }
        }
      }
      _flashingTimer--;
      if (_flashingTimer < 0) {
        _flashingTimer = _flashingDurationB;
        ExtensionMethods.Swap (ref _flashingDurationA, ref _flashingDurationB);
        _entityMedic1.Bit3_IsTeam1_IsElectrobounced = !_entityMedic1.Bit3_IsTeam1_IsElectrobounced;
        _entityMedic2.Bit3_IsTeam1_IsElectrobounced = !_entityMedic2.Bit3_IsTeam1_IsElectrobounced;
        _entityPlayerIndicatorTeam1.Bit7_IsInvisible = !_entityPlayerIndicatorTeam1.Bit7_IsInvisible;
        _entityPlayerIndicatorTeam2.Bit7_IsInvisible = !_entityPlayerIndicatorTeam2.Bit7_IsInvisible;
        _entityGoalIndicatorLeft.Bit7_IsInvisible = !_entityGoalIndicatorLeft.Bit7_IsInvisible;
        _entityGoalIndicatorRight.Bit7_IsInvisible = !_entityGoalIndicatorRight.Bit7_IsInvisible;
      }
    }

    private void IncrementTimeInPossession () {
      if (((_entityBall._terrainXY.Y <= 576) && (_playersTeam1 [0]._attributes._initialDir == 0)) ||
          ((_entityBall._terrainXY.Y > 576) && (_playersTeam1 [0]._attributes._initialDir != 0))) {
        _timeInOpponentsHalfTeam1++;
      }
      else {
        _timeInOpponentsHalfTeam2++;
      }
      if (_entityHoldingBall != null) {
        Player player = _entityHoldingBall.GetPlayer;
        player._timeInPossession++;
      }
    }

    private void DrawTerrainWallsAndBackground () {
      if (_noDrawing)
        return;
      SetClipHeight (_viewHeight);
      _g.DrawImage (_field, -_scrollXY.X, -_scrollXY.Y);
      SetClipHeight (-1);
    }

    private void DrawSprites () {
      if (_noDrawing)
        return;
      // todo: init replay
      DrawEntity (_entityDeck);
      DrawEntity (_entityDeck);
      if (SpriteManager.Version == VersionEnum.Atari) {
        DrawEntity (_bounceDomes.EntityTop);
        DrawEntity (_bounceDomes.EntityBottom);
      }
      else if (SpriteManager.Version == VersionEnum.Amiga) {
        if (_bounceDomes.EntityTop._spriteIndex == 0x88)
          DrawEntity (_bounceDomes.EntityTop);
        if (_bounceDomes.EntityBottom._spriteIndex == 0x88)
          DrawEntity (_bounceDomes.EntityBottom);
      }
      else
        throw new Exception ();


      foreach (Entity entity in _scoreMultipliers._entities)
        DrawEntity (entity);
      // hack
      DrawEntity (Electrobounces.EntityLeft);
      DrawEntity (Electrobounces.EntityRight);
      _stars.Draw (this);
      DrawItems ();
      OrderPlayersAndMedics ();
      int drawingOrder = -1;
      if (_playerInjured != null) {
        if (_entityBall._velocityXY.IsEmpty)
          drawingOrder = 2;
        else
          drawingOrder = 1;
      }
      else {
        if ((_entityHoldingBall == _entityTerrainCenter) ||
            (_entityHoldingBall == null)) {
          if (_entityBall._spriteIndex > 2)
            drawingOrder = 1;
          else
            if (_entityBall._velocityXY.IsEmpty)
              drawingOrder = 2;
            else
              drawingOrder = 1;
        }
        else {
          if (!_entityHoldingBall.Bit1_IsJumping)
            drawingOrder = 1;
          else {
            if ((_entityHoldingBall._dir < 3) || (_entityHoldingBall._dir > 5))
              drawingOrder = 2;
            else
              drawingOrder = 1;
          }
        }
      }
      if (drawingOrder == 1) {
        DrawPlayersAndMedic ();
        DrawEntity (_entityBall);
      }
      else if (drawingOrder == 2) {
        DrawEntity (_entityBall);
        DrawPlayersAndMedic ();
      }
      else
        throw new Exception ();
      DrawIndicatorsPlayersAndGoal ();
      DrawEntity (_entityBallHighLauncher);
    }

    private void DrawItems () {
      DrawEntity (_entityItemToken1);
      DrawEntity (_entityItemToken2);
      DrawEntity (_entityItemEquipment);
      DrawEntity (_entityItemCoins1);
      DrawEntity (_entityItemCoins2);
      DrawEntity (_entityItemCoins3);
      DrawEntity (_entityItemCoins4);
    }

    private void OrderPlayersAndMedics () {
      for (; ; ) {
        bool again = false;
        for (int i = 0; i < 19; i++) {
          Entity a0_entity = _orderedEntities [i];
          Entity a1_entity = _orderedEntities [i + 1];
          int y0 = a0_entity._terrainXY.Y;
          if (a0_entity.Bit4_IsFalling)
            y0 -= 16;
          int y1 = a1_entity._terrainXY.Y;
          if (a1_entity.Bit4_IsFalling)
            y1 -= 16;
          if (y0 > y1) {
            ExtensionMethods.Swap (ref _orderedEntities [i], ref _orderedEntities [i + 1]);
            again = true;
          }
        }
        if (!again)
          break;
      }
    }

    private void DrawPlayersAndMedic () {
      foreach (Entity entity in _orderedEntities)
        DrawEntity (entity);
    }

    public void DrawEntity (Entity entity) {
      SetClipHeight (_viewHeight);
      entity.Draw (_g, _scrollXY);
      SetClipHeight (-1);
    }

    public void DrawTile (Bitmap bitmap, int x, int y) {
      SetClipHeight (_viewHeight);
      _g.DrawImage (bitmap, x * 32 - _scrollXY.X, y * 32 - _scrollXY.Y);
      SetClipHeight (-1);
    }

    public void DrawTileAmiga (Bitmap bitmap, int x, int y) {
      SetClipHeight (_viewHeight);
      _g.DrawImage (bitmap, x * 16 - _scrollXY.X, y * 16 - _scrollXY.Y);
      SetClipHeight (-1);
    }

    private void DrawIndicatorsPlayersAndGoal () {
      if (Bit7_IsClockPaused)
        return;
      DrawEntity (_entityPlayerIndicatorTeam1);
      if (_gameType != GameTypeEnum.Practice)
        DrawEntity (_entityPlayerIndicatorTeam2);
      DrawEntity (_entityGoalIndicatorLeft);
      DrawEntity (_entityGoalIndicatorRight);
    }

    private void CenterScreenOnEntity (Entity entity) {
      int d0 = entity._terrainXY.X - 160;
      int scrollX = _scrollXY.X;
      int d2 = CenterScreenOnEntity_Helper (d0, scrollX);
      if ((_leg >= 2) || (d2 >= 2)) {
        if (d0 > scrollX) {
          scrollX += d2;
          if (scrollX > d0)
            scrollX = d0;
          if (scrollX > 320)
            scrollX = 320;
        }
        else if (d0 < scrollX) {
          scrollX -= d2;
          if (scrollX < d0)
            scrollX = d0;
          if (scrollX < 0)
            scrollX = 0;
        }
      }
      _scrollXY.X = scrollX;

      d0 = entity._terrainXY.Y - 92;
      int scrollY = _scrollXY.Y;
      d2 = CenterScreenOnEntity_Helper (d0, scrollY);
      if (d0 > scrollY) {
        scrollY += d2;
        if (scrollY > d0)
          scrollY = d0;
        if (scrollY > 968)
          scrollY = 968;
      }
      else if (d0 < scrollY) {
        scrollY -= d2;
        if (scrollY < d0)
          scrollY = d0;
        if (scrollY < 0)
          scrollY = 0;
      }
      _scrollXY.Y = scrollY;

      if (scrollY <= 484)
        scrollY++;
      else
        scrollY += 183;

      _entityGoalIndicatorLeft._terrainXY.Y = scrollY;
      _entityGoalIndicatorRight._terrainXY.Y = scrollY;
    }

    private int CenterScreenOnEntity_Helper (int d0, int d1) {
      int d2 = d0 - d1;
      if (d2 < 0)
        d2 = -d2;
      d2 >>= 3;
      d2++;
      if (d2 > 16)
        d2 = 16;
      return (d2);
    }

    private void HandleAnimGoalToInitLaunch () {
      if (Bit5_IsLauncherAnimStarted)
        return;
      if (_entityHoldingBall != null) {
        if (_entityHoldingBall.Bit0_IsBusy)
          return;
        if (_textPanel._text == TextEnum.None)
          _textPanel._text = TextEnum.VS;
      }
      _entityHoldingBall = _entityTerrainCenter;
      _entityTerrainCenter._terrainXY = new Point (320, 576);
      _entityTerrainCenter.Bit0_IsBusy = false;
      if (_scrollXY.Y != 484)
        return;
      _entityBall.Bit7_IsInvisible = true;
      _token.Dispose (this);
      foreach (Player player in _playersTeam1) {
        if (player._terrainXY != player._launchXY)
          return;
        if (player._dir != player._attributes._initialDir)
          return;
      }
      foreach (Player player in _playersTeam2) {
        if (player._terrainXY != player._launchXY)
          return;
        if (player._dir != player._attributes._initialDir)
          return;
      }
      _entityDeck._opcodeIndex = 0;
      _entityHoldingBall = null;
      Bit5_IsLauncherAnimStarted = true;
      _entityBall.Bit7_IsInvisible = true;
      _entityBall._terrainXY = new Point (320, 576);
      _entityBall._velocityXY = Point.Empty;
      Bit2_IsBallInScoreMultiplier = false;
    }

    private void HandleDeck () {
      if (!Bit5_IsLauncherAnimStarted)
        return;
      if (_entityDeck._opcodeIndex < 19)
        return;
      if (_entityDeck._opcodeIndex != 19) {
        if (_entityBall._opcodeIndex < 11)
          return;
        if (_entityBall._opcodeIndex != 11) {
          if (_entityBall._opcodeIndex != 21)
            return;
          // 21
          if (_leg != 2)
            Bit7_IsClockPaused = false;
          Bit5_IsLauncherAnimStarted = false;
          _input1.Reset ();
          _input2.Reset ();
        }
        // 11
        else
          _entityBallHighLauncher.Bit7_IsInvisible = false;
      }
      // 19
      else {
        _entityBall.Bit7_IsInvisible = false;
        _entityBall._pOpcodes = 0x561E;
        _entityBall._opcodeIndex = 0;
      }
    }

    public void IncScoreWithBonusPoints (bool side, int value) {
      if (side) {
        IncScoreWithBonusPoints_Helper (ref _scoreTeam1, ref _bonusPointsTeam1, value);
        if (_scoreMultipliers._team1 != 0) {
          if (_scoreMultipliers._team1 == 1)
            IncScoreWithBonusPoints_Helper (ref _scoreTeam1, ref _bonusPointsTeam1, value / 2);
          else
            IncScoreWithBonusPoints_Helper (ref _scoreTeam1, ref _bonusPointsTeam1, value);
        }
      }
      else {
        IncScoreWithBonusPoints_Helper (ref _scoreTeam2, ref _bonusPointsTeam2, value);
        if (_scoreMultipliers._team2 != 0) {
          if (_scoreMultipliers._team2 == 1)
            IncScoreWithBonusPoints_Helper (ref _scoreTeam2, ref _bonusPointsTeam2, value / 2);
          else
            IncScoreWithBonusPoints_Helper (ref _scoreTeam2, ref _bonusPointsTeam2, value);
        }
      }
    }

    private void IncScoreWithBonusPoints_Helper (ref int score, ref int bonusPoints, int value) {
      score += value;
      if (!_entityBall.Bit7_IsInvisible)
        bonusPoints += value;
    }

    public bool Bit0_IsComputerPlayer1 {
      get {
        return (_flags [_bit0_isComputerPlayer1]);
      }
      set {
        _flags [_bit0_isComputerPlayer1] = value;
      }
    }

    public bool Bit1_IsComputerPlayer2 {
      get {
        return (_flags [_bit1_isComputerPlayer2]);
      }
      set {
        _flags [_bit1_isComputerPlayer2] = value;
      }
    }

    public bool Bit2_IsBallInScoreMultiplier {
      get {
        return (_flags [_bit2_isBallInScoreMultiplier]);
      }
      set {
        _flags [_bit2_isBallInScoreMultiplier] = value;
      }
    }

    public bool Bit3_IsPlayerInjured {
      get {
        return (_flags [_bit3_isPlayerInjured]);
      }
      set {
        _flags [_bit3_isPlayerInjured] = value;
      }
    }

    public bool Bit4_IsFastForward {
      get {
        return (_flags [_bit4_isFastForward]);
      }
      set {
        _flags [_bit4_isFastForward] = value;
      }
    }

    public bool Bit5_IsLauncherAnimStarted {
      get {
        return (_flags [_bit5_isLauncherAnimStarted]);
      }
      set {
        _flags [_bit5_isLauncherAnimStarted] = value;
      }
    }

    public bool Bit6_ExitMatch {
      get {
        return (_flags [_bit6_exitMatch]);
      }
      set {
        _flags [_bit6_exitMatch] = value;
      }
    }

    public bool Bit7_IsClockPaused {
      get {
        return (_flags [_bit7_isClockPaused]);
      }
      set {
        _flags [_bit7_isClockPaused] = value;
      }
    }

    public Bitmap Screen {
      get {
        return (_screen);
      }
    }

    public Point ScrollXY {
      get {
        return (_scrollXY);
      }
    }

    public GameTypeEnum GameType {
      get {
        return (_gameType);
      }
    }

    public int Leg {
      get {
        return (_leg);
      }
    }

    public Player PlayerHavingInputTeam1 {
      get {
        return (_playerHavingInputTeam1);
      }
      set {
        _playerHavingInputTeam1 = value;
      }
    }

    public Player PlayerHavingInputTeam2 {
      get {
        return (_playerHavingInputTeam2);
      }
      set {
        _playerHavingInputTeam2 = value;
      }
    }

    public Player PlayerThrower {
      get {
        return (_playerThrower);
      }
      set {
        _playerThrower = value;
      }
    }

    public Entity EntityHoldingBall {
      get {
        return (_entityHoldingBall);
      }
      set {
        _entityHoldingBall = value;
      }
    }

    public Entity EntityTerrainCenter {
      get {
        return (_entityTerrainCenter);
      }
      set {
        _entityTerrainCenter = value;
      }
    }

    public Entity EntityBall {
      get {
        return (_entityBall);
      }
    }

    public Entity EntityMedic1 {
      get {
        return (_entityMedic1);
      }
    }

    public Entity EntityMedic2 {
      get {
        return (_entityMedic2);
      }
    }

    public Entity EntityStatusLed {
      get {
        return (_entityStatusLed);
      }
    }

    public Entity EntityItemToken1 {
      get {
        return (_entityItemToken1);
      }
    }

    public Entity EntityItemToken2 {
      get {
        return (_entityItemToken2);
      }
    }

    public Entity EntityItemEquipment {
      get {
        return (_entityItemEquipment);
      }
    }

    public Entity EntityItemCoins1 {
      get {
        return (_entityItemCoins1);
      }
    }

    public Entity EntityItemCoins2 {
      get {
        return (_entityItemCoins2);
      }
    }

    public Entity EntityItemCoins3 {
      get {
        return (_entityItemCoins3);
      }
    }

    public Entity EntityItemCoins4 {
      get {
        return (_entityItemCoins4);
      }
    }

    public Player [] PlayersTeam1 {
      get {
        return (_playersTeam1);
      }
    }

    public Player [] PlayersTeam2 {
      get {
        return (_playersTeam2);
      }
    }

    public int ScoreTeam1 {
      get {
        return (_scoreTeam1);
      }
      set {
        _scoreTeam1 = value;
      }
    }

    public int ScoreTeam2 {
      get {
        return (_scoreTeam2);
      }
      set {
        _scoreTeam2 = value;
      }
    }

    public ScoreMultipliers ScoreMultipliers {
      get {
        return (_scoreMultipliers);
      }
    }

    public Electrobounces Electrobounces {
      get {
        return (_electrobounces);
      }
    }

    public bool IsOnscreen (Point p) {
      if ((p.Y - _scrollXY.Y) >= 0)
        if ((p.Y - _scrollXY.Y) <= 184)
          if ((p.X - _scrollXY.X) >= 0)
            if ((p.X - _scrollXY.X) <= 320)
              return (true);
      return (false);
    }

    public int BallSpeedUnk {
      get {
        return (_ballSpeedUnk);
      }
      set {
        _ballSpeedUnk = value;
      }
    }

    public Attributes GetPersonAttributes (int personAttributesIndex, AttributesTypeEnum personAttributesType) {
      if (personAttributesType == AttributesTypeEnum.Team1)
        return (_personAttributesTeam1 [personAttributesIndex]);
      if (personAttributesType == AttributesTypeEnum.Team2)
        return (_personAttributesTeam2 [personAttributesIndex]);
      if (personAttributesType == AttributesTypeEnum.Temp)
        return (_personAttributesTemp);
      if (personAttributesType == AttributesTypeEnum.Market)
        return (_personAttributesMarket [personAttributesIndex]);
      throw new Exception ();
    }

    public static int GetOpcodesFromOpcodesList (int pOpcodesList, int index) {
      int offset = pOpcodesList + index * 4;
      return ((int) Game.AtariDisk.ReadLong (Game.AtariRamToDisk (offset)));
    }

    public static int GetTackleDefMalus (int deltaDir) {
      int offset = 0x582 + deltaDir;
      return (Game.AtariDisk.ReadByte (Game.AtariRamToDisk (offset)));
    }

    public static int GetMirrorXFromDir (int dir) {
      int offset = 0x6086 + dir;
      return (Game.AtariDisk.ReadByte (Game.AtariRamToDisk (offset)));
    }

    public static int GetMirrorYFromDir (int dir) {
      int offset = 0x608E + dir;
      return (Game.AtariDisk.ReadByte (Game.AtariRamToDisk (offset)));
    }

    public static int GetDirBallBlockedByGoal (int dir, int initialDir) {
      int offset = 0xD714 + dir;
      if (initialDir != 0)
        offset += 8;
      return (Game.AtariDisk.ReadByte (Game.AtariRamToDisk (offset)));
    }

    public static Point GetVelocityXYFromDir (int dir, int velocity) {
      int index = 32 * velocity + dir * 4;
      int offset = 0x6096 + index;
      return (new Point (Game.AtariDisk.ReadWordSigned (Game.AtariRamToDisk (offset)),
                         Game.AtariDisk.ReadWordSigned (Game.AtariRamToDisk (offset + 2))));
    }

    public const int SpeedMaxForVelocity4 = 140;
    public const int SpeedMaxForVelocity5 = 170;
    public const int SpeedMaxForVelocity6 = 200;

    public static Match Instance {
      get {
        return (_instance);
      }
    }
  }

  public enum GameTypeEnum {
    Knockout,
    League,
    Cup,
    ReplayGoals,
    Demo,
    TwoPlayers,
    Practice
  }
}
